package at.ac.tuwien.dse.health;

import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.RequestSpecification;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import static com.jayway.restassured.RestAssured.given;
import static org.junit.Assert.assertEquals;

/**
 * @author Bernhard Nickel, e0925384, e0925384@student.tuwien.ac.at
 */
public class HealthLoadIT {
	public static final int SLOTS = 500;
	protected static ExecutorService EXECUTOR_SERVICE = createExecutorService();

	protected static final String COOKIE_SESSION_ID_KEY = "JSESSIONID";
//	protected static final String URL = "http://dse-health.cloudfoundry.com";
	protected static final String URL = "http://localhost:8080/health";

	protected static final String[] SURGERY_TYPES = {"Augenheilkunde", "Orthopädie", "HNNO", "Neurochirurgie", "Kardiologie"};

	protected static final long HOUR = 1000 * 60 * 60;

	protected DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");

	protected List<Long> createSlotDurations;
	protected List<Long> reserveDurations;

	private static enum Parameter {
		MAX, MIN, AVERAGE
	}

	static ExecutorService createExecutorService() {
		return Executors.newCachedThreadPool();
	}

	@Before
	public void before() {
		createSlotDurations = new ArrayList<Long>();
		reserveDurations = new ArrayList<Long>();
	}

	@After
	public void after() {
		createSlotDurations.clear();
		reserveDurations.clear();
	}


	@Test
	public void testScenario() throws ParseException, InterruptedException {
		Date startdate = dateFormat.parse("2013-08-01 00:00");

		List<String> sessionIds = new ArrayList<String>();
		sessionIds.add(createSlots("akhw", "akhw", startdate, HOUR, SLOTS));
		sessionIds.add(createSlots("lkhk", "lkhk", startdate, HOUR, SLOTS));
		sessionIds.add(createSlots("bbw", "bbw", startdate, HOUR, SLOTS));
		sessionIds.add(createSlots("lkht", "lkht", startdate, HOUR, SLOTS));

		EXECUTOR_SERVICE.shutdown();
		EXECUTOR_SERVICE.awaitTermination(1, TimeUnit.MINUTES);
		assertEquals(true, EXECUTOR_SERVICE.shutdownNow().isEmpty());

		for (String sessionId : sessionIds) {
			logout(sessionId);
		}
		sessionIds.clear();


		EXECUTOR_SERVICE = createExecutorService();
		sessionIds.add(reserve("aa", "aa", 200, SLOTS));
		sessionIds.add(reserve("gg", "gg", 200, SLOTS));
		sessionIds.add(reserve("rg", "rg", 200, SLOTS));
		sessionIds.add(reserve("ka", "ka", 200, SLOTS));

		EXECUTOR_SERVICE.shutdown();
		EXECUTOR_SERVICE.awaitTermination(1, TimeUnit.MINUTES);
		assertEquals(true, EXECUTOR_SERVICE.shutdownNow().isEmpty());

		for (String sessionId : sessionIds) {
			logout(sessionId);
		}
		sessionIds.clear();


		Map<Parameter, Double> createRequestStats = getParameters(createSlotDurations);
		Map<Parameter, Double> reserveRequestStats = getParameters(reserveDurations);

		System.out.println("Create slot max duration: " + createRequestStats.get(Parameter.MAX));
		System.out.println("Create slot min duration: " + createRequestStats.get(Parameter.MIN));
		System.out.println("Create slot average duration: " + createRequestStats.get(Parameter.AVERAGE));

		System.out.println("Reserve max duration: " + reserveRequestStats.get(Parameter.MAX));
		System.out.println("Reserve min duration: " + reserveRequestStats.get(Parameter.MIN));
		System.out.println("Reserve average duration: " + reserveRequestStats.get(Parameter.AVERAGE));
	}

	protected String reserve(String user, String password, final int maxKilometer, int amount) {
		final String sessionId = login(user, password);

		final Random random = new Random();

		for (int i = 0; i < amount; i++) {
			EXECUTOR_SERVICE.submit(new Runnable() {
				@Override
				public void run() {
					int patient = 1001 + random.nextInt(6);
					String surgeryType = SURGERY_TYPES[random.nextInt(SURGERY_TYPES.length)];

					long now = System.currentTimeMillis();
					Response response = loginGiven(sessionId).param("patient", patient).param("maxKilometers", maxKilometer).param("surgeryType", surgeryType).get(URL + "/dashboard/reservation/request");
					if (response.getStatusCode() != 200) {
						System.out.println(response.getStatusLine());
					}
					createSlotDurations.add(System.currentTimeMillis() - now);
				}
			});
		}

		return sessionId;
	}

	protected String createSlots(String user, String password, Date startdate, final long slotLength, int slots) {
		final String sessionId = login(user, password);

		final AtomicLong timestamp = new AtomicLong(startdate.getTime());

		for (int i = 0; i < slots; i++, timestamp.addAndGet(slotLength)) {
			EXECUTOR_SERVICE.submit(new Runnable() {
				@Override
				public void run() {
					long now = System.currentTimeMillis();
					Response response = loginGiven(sessionId).param("startDate", String.valueOf(timestamp.get())).param("endDate", String.valueOf(timestamp.get() + slotLength)).when().get(URL + "/dashboard/hospital/createSlot");
					if (response.getStatusCode() != 200) {
						System.out.println(response.getStatusLine());
					}
					reserveDurations.add(System.currentTimeMillis() - now);
				}
			});
		}

		return sessionId;
	}

	protected String login(String username, String password) {
		return given().param("username", username).param("password", password).expect().statusCode(302).when().post(URL + "/dashboard/login").cookie(COOKIE_SESSION_ID_KEY);
	}

	protected RequestSpecification loginGiven(String sessionId) {
		return given().cookie(COOKIE_SESSION_ID_KEY, sessionId);
	}

	protected void logout(String sessionId) {
		if (sessionId != null) {
			loginGiven(sessionId).expect().statusCode(200).when().get(URL + "/dashboard/login");
		}
	}

	protected static Map<Parameter, Double> getParameters(List<? extends Number> values) {
		Double max = Double.MIN_VALUE;
		Double min = Double.MAX_VALUE;
		Double sum = 0d;

		for (Number n : values) {
			Double d = n.doubleValue();

			if (d > max) {
				max = d;
			}

			if (d < min) {
				min = d;
			}

			sum += d;
		}

		Map<Parameter, Double> parameters = new HashMap<Parameter, Double>();
		parameters.put(Parameter.MAX, max);
		parameters.put(Parameter.MIN, min);
		parameters.put(Parameter.AVERAGE, sum / values.size());

		return parameters;
	}
}
